package com.grupoconstruobras.siijt.exhumaciones.ui.mapper;

import com.grupoconstruobras.sigo.datatransfer.IParametroDto;
import com.grupoconstruobras.sigo.datatransfer.TemporalParametroDto;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.LocalBean;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Schedule;
import javax.ejb.SessionContext;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.faces.model.SelectItem;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import javax.persistence.Query;

@LocalBean
@Singleton
@Startup
public class SelectItemFactory {
	
	enum State {
		STARTUP, SCHEDULE, NON
	}
	enum TypeQuery {
		NAMED("Named"), NATIVE("Native"), JPQL("JPQL");
		private String type;
		private TypeQuery(String type) {
			this.type = type;
		}
		@Override
		public String toString() {
			return type;
		}
		public boolean equals(String key) {
			return type.equalsIgnoreCase((String) key);
		}
	}
	enum EntityManagerType {
		DEFAULT("default")/*, FISCALIA("fiscalia")*/;
		private String type;
		private EntityManagerType(String type) {
			this.type = type;
		}
		@Override
		public String toString() {
			return this.type;
		}
		public boolean equals(String key) {
			return type.equalsIgnoreCase((String) key);
		}
	}
	
	private final Logger LOG = Logger.getLogger(SelectItemFactory.class.getName());
	private State state;
	private final static String CATALOGOS_QUERIES = "SelectItems.properties";
	private Properties queries;
	private static final String JNDI_ENV = "java:comp/env/persistence/";
	
	@Resource
	private SessionContext ctx;
	@PersistenceUnit(unitName="default")
	private EntityManagerFactory entityManagerFactory;
	
	
	public SelectItemFactory() {
		queries = new Properties();
		state = State.NON;
	}
	
	@PostConstruct
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public void init() {
		if(state == State.NON) {
			state = State.STARTUP;
			refresh();
			state = State.NON;
		}
	}
	
	@Schedule(hour="*", minute="30")
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public void schedule() {
		if(state == State.NON) {
			state = State.SCHEDULE;
			refresh();
			state = State.NON;
		}
	}
	
	@Lock(LockType.WRITE)
	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public void refresh() {
		try {
			queries.clear();
			queries.load(this.getClass().getClassLoader().getResourceAsStream(CATALOGOS_QUERIES));
		} catch(IllegalArgumentException e) {
			LOG.severe(e.getMessage());
		} catch(IOException e) {
			LOG.severe(e.getMessage());
		}
		
		for(final Object keyQuery : queries.keySet()) {
			LOG.info("********************************************************************");
			StringTokenizer token = new StringTokenizer((String) keyQuery, "\\.");
			LOG.info("Query: " + (String) token.nextToken());
			
			EntityManager entityManager = getEntityManager(token.nextToken());
			final TypeQuery type = getQueryType(token.nextToken());
			
			if(token.hasMoreTokens() && token.nextToken().equalsIgnoreCase("cache")) {
				LOG.info("Is cacheable");
				
				final String squery = queries.getProperty((String) keyQuery);
				Query query = null;
				if(type == TypeQuery.JPQL) {
					query = entityManager.createQuery(squery, SelectItem.class);
				}
				else if(type == TypeQuery.NAMED) {
					query = entityManager.createNamedQuery(squery, SelectItem.class);
				}
				else if(type == TypeQuery.NATIVE) {
					query = entityManager.createNativeQuery(squery);
				}
				query.setHint("javax.persistence.cache.storeMode", CacheStoreMode.REFRESH);
				query.setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.BYPASS);
				query.getResultList();
			} else
				LOG.info("Is not cacheable");
			LOG.info("********************************************************************");
		}
	}
	
	@Lock(LockType.READ)
	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public List<SelectItem> get(final String keyQuery, IParametroDto ... parametros) {
		final String squery = queries.getProperty(keyQuery);
		if(squery == null || squery.isEmpty())
			return new ArrayList<SelectItem>();
		
		LOG.info("********************************************************************");
		StringTokenizer token = new StringTokenizer(keyQuery, "\\.");
		LOG.info("Query: " + (String) token.nextToken());
		
		EntityManager entityManager = getEntityManager(token.nextToken());
		final TypeQuery type = getQueryType(token.nextToken());
		Query query = null;
		
		if(type == TypeQuery.JPQL) {
			query = entityManager.createQuery(squery, SelectItem.class);
		}
		else if(type == TypeQuery.NAMED) {
			query = entityManager.createNamedQuery(squery, SelectItem.class);
		}
		else if(type == TypeQuery.NATIVE) {
			query = entityManager.createNativeQuery(squery);
		}
		
		setParameters(query, parametros);
		if(state == State.NON && token.hasMoreTokens() && token.nextToken().equalsIgnoreCase("cache")) {
			query.setHint("javax.persistence.cache.storeMode", CacheStoreMode.USE);
			query.setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE);
		} else {
			query.setHint("javax.persistence.cache.storeMode", CacheStoreMode.BYPASS);
			query.setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.BYPASS);
		}
		
		LOG.info("********************************************************************");
		if(type == TypeQuery.NAMED || type == TypeQuery.JPQL)
			return query.getResultList();
		else
			return new ObjectMapper().mapper(query.getResultList());
	}
	
	@Lock(LockType.READ)
	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public Object getCacheStringValue(final String keyQuery) {
		
		final String squery = queries.getProperty(keyQuery);		
		
		LOG.info("********************************************************************");
		StringTokenizer token = new StringTokenizer(keyQuery, "\\.");
		LOG.info("Query: " + (String) token.nextToken());
		
		EntityManager entityManager = getEntityManager(token.nextToken());
		final TypeQuery type = getQueryType(token.nextToken());
		Query query = null;
		
		if(type == TypeQuery.JPQL) {
			query = entityManager.createQuery(squery, SelectItem.class);
		}
		else if(type == TypeQuery.NAMED) {
			query = entityManager.createNamedQuery(squery, SelectItem.class);
		}
		else if(type == TypeQuery.NATIVE) {
			query = entityManager.createNativeQuery(squery);
		}
		
		setParameters(query);
		if(state == State.NON && token.hasMoreTokens() && token.nextToken().equalsIgnoreCase("cache")) {
			query.setHint("javax.persistence.cache.storeMode", CacheStoreMode.USE);
			query.setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE);
		} else {
			query.setHint("javax.persistence.cache.storeMode", CacheStoreMode.BYPASS);
			query.setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.BYPASS);
		}
		
		LOG.info("********************************************************************");
		if(type == TypeQuery.NAMED || type == TypeQuery.JPQL)
			return query.getSingleResult();
		else
			return query.getSingleResult();
	}
	
	
	
	
	@Lock(LockType.READ)
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	private void setParameters(Query query, IParametroDto ... parametros) {
		if(parametros == null || parametros.length == 0)
			return;
		
		for(IParametroDto parametro : parametros) {
			if(parametro instanceof TemporalParametroDto) {
				if(parametro.isKeyInt() && parametro.getValue() instanceof Calendar)
					query.setParameter(parametro.getKeyAsInt(), (Calendar) parametro.getValue(), ((TemporalParametroDto)parametro).getType());
				else if(parametro.isKeyInt() && parametro.getValue() instanceof Date)
					query.setParameter(parametro.getKeyAsInt(), (Date) parametro.getValue(), ((TemporalParametroDto)parametro).getType());
				else if(!parametro.isKeyInt() && parametro.getValue() instanceof Calendar)
					query.setParameter(parametro.getKeyAsString(), (Calendar) parametro.getValue(), ((TemporalParametroDto)parametro).getType());
				else if(!parametro.isKeyInt() && parametro.getValue() instanceof Date)
					query.setParameter(parametro.getKeyAsString(), (Date) parametro.getValue(), ((TemporalParametroDto)parametro).getType());
			} else {
				if(parametro.isKeyInt())
					query.setParameter(parametro.getKeyAsInt(), parametro.getValue());
				else if(!parametro.isKeyInt())
					query.setParameter(parametro.getKeyAsString(), parametro.getValue());
			}
		}
	}
	
	@Lock(LockType.READ)
	@TransactionAttribute(TransactionAttributeType.REQUIRED)
	private EntityManager getEntityManager(final String key) {
		LOG.info("Persistent Unit: " + key);
		String jndi = null;
		EntityManager entityManager = null;
		/*if(EntityManagerType.default.equals(key)) {
			entityManager = entityManagerFactoryFiscalia.createEntityManager();
		} else */if(EntityManagerType.DEFAULT.equals(key)) {
			entityManager = this.entityManagerFactory.createEntityManager();
		} else {
			entityManager = this.entityManagerFactory.createEntityManager();
		}
		return entityManager;
	}
	
	@Lock(LockType.READ)
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	private TypeQuery getQueryType(final String key) {
		LOG.info("Type: " + key);
		if(TypeQuery.JPQL.equals(key)) {
			return TypeQuery.JPQL;
		} else if(TypeQuery.NAMED.equals(key)) {
			return TypeQuery.NAMED;
		} else if(TypeQuery.NATIVE.equals(key)) {
			return TypeQuery.NATIVE;
		} else {
			return TypeQuery.NAMED;
		}
	}

}
